package edu.northwestern.cbits.purple_robot_manager.triggers; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.preference.PreferenceCategory; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; import android.support.v4.util.LongSparseArray; import edu.emory.mathcs.backport.java.util.Collections; import edu.northwestern.cbits.purple_robot_manager.ManagerService; import edu.northwestern.cbits.purple_robot_manager.R; import edu.northwestern.cbits.purple_robot_manager.activities.settings.SettingsKeys; import edu.northwestern.cbits.purple_robot_manager.config.SchemeConfigFile; import edu.northwestern.cbits.purple_robot_manager.logging.LogManager; import edu.northwestern.cbits.purple_robot_manager.scripting.BaseScriptEngine; import edu.northwestern.cbits.purple_robot_manager.util.WakeLockManager; public class TriggerManager { private static TriggerManager _instance = null; private final List<Trigger> _triggers = new ArrayList<>(); private Timer _timer = null; private boolean _triggersInited = false; private TriggerManager(Context context) { if (TriggerManager._instance != null) throw new IllegalStateException("Already instantiated"); } public static TriggerManager getInstance(Context context) { if (TriggerManager._instance == null) { TriggerManager._instance = new TriggerManager(context.getApplicationContext()); TriggerManager._instance.restoreTriggers(context); } return TriggerManager._instance; } @SuppressLint("Wakelock") public void nudgeTriggers(Context context) { WakeLock wakeLock = WakeLockManager.getInstance(context).requestWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "trigger_manager_wakelock"); Date now = new Date(); synchronized (this._triggers) { for (Trigger trigger : this._triggers) { boolean execute = false; if (trigger instanceof DateTrigger) { if (trigger.matches(context, now)) execute = true; } if (execute) { trigger.execute(context, false); } } } WakeLockManager.getInstance(context).releaseWakeLock(wakeLock); } public void updateTriggers(Context context, List<Trigger> triggerList) { ArrayList<Trigger> toAdd = new ArrayList<>(); synchronized (this._triggers) { for (Trigger newTrigger : triggerList) { boolean found = false; for (Trigger trigger : this._triggers) { if (trigger.equals(newTrigger)) { trigger.merge(newTrigger); found = true; } } if (!found) toAdd.add(newTrigger); } if (toAdd.size() > 0) ManagerService.resetTriggerNudgeDate(); this._triggers.addAll(toAdd); } this.persistTriggers(context); } private void restoreTriggers(Context context) { if (this._triggersInited) return; this._triggersInited = true; SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); if (prefs.contains("triggers_scheme_script")) { String script = prefs.getString("triggers_scheme_script", "(begin)"); try { BaseScriptEngine.runScript(context, script); } catch (Exception e) { LogManager.getInstance(context).logException(e); } } } protected void persistTriggers(final Context context) { if (this._timer == null) { this._timer = new Timer(); final TriggerManager me = this; this._timer.schedule(new TimerTask() { @Override public void run() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); Editor e = prefs.edit(); SchemeConfigFile config = new SchemeConfigFile(context); String script = config.triggersScript(context); e.putString("triggers_scheme_script", script); e.commit(); me._timer = null; } }, 5000); } Intent intent = new Intent(ManagerService.UPDATE_TRIGGER_SCHEDULE_INTENT); intent.setClass(context, ManagerService.class); context.startService(intent); } public List<Trigger> triggersForId(String triggerId) { ArrayList<Trigger> matches = new ArrayList<>(); synchronized (this._triggers) { for (Trigger trigger : this._triggers) { if (trigger.identifier() != null && trigger.identifier().equals(triggerId)) matches.add(trigger); } } return matches; } public List<Trigger> allTriggers() { return this._triggers; } public void addTrigger(Context context, Trigger t) { ArrayList<Trigger> ts = new ArrayList<>(); ts.add(t); this.updateTriggers(context, ts); } public void removeAllTriggers() { synchronized (this._triggers) { this._triggers.clear(); } } @SuppressWarnings("deprecation") public PreferenceScreen buildPreferenceScreen(Context context, PreferenceManager manager) { PreferenceScreen screen = manager.createPreferenceScreen(context); screen.setOrder(0); screen.setTitle(R.string.title_preference_triggers_screen); screen.setKey(SettingsKeys.TRIGGERS_SCREEN_KEY); PreferenceCategory triggersCategory = new PreferenceCategory(context); triggersCategory.setTitle(R.string.title_preference_triggers_category); triggersCategory.setKey("key_available_triggers"); screen.addPreference(triggersCategory); synchronized (this._triggers) { for (Trigger trigger : this._triggers) { PreferenceScreen triggerScreen = trigger.preferenceScreen(context, manager); if (triggerScreen != null) screen.addPreference(triggerScreen); } } return screen; } public List<Map<String, Object>> triggerConfigurations(Context context) { List<Map<String, Object>> configs = new ArrayList<>(); synchronized (this._triggers) { for (Trigger t : this._triggers) { Map<String, Object> config = t.configuration(context); configs.add(config); } } return configs; } public void refreshTriggers(Context context) { synchronized (this._triggers) { for (Trigger t : this._triggers) t.refresh(context); } } public List<String> triggerIds() { ArrayList<String> triggerIds = new ArrayList<>(); synchronized (this._triggers) { for (Trigger t : this._triggers) { String id = t.identifier(); if (id != null && triggerIds.contains(id) == false) triggerIds.add(id); } } return triggerIds; } public Map<String, Object> fetchTrigger(Context context, String id) { List<Trigger> triggers = this.triggersForId(id); if (triggers.size() > 0) return triggers.get(0).configuration(context); return null; } public boolean deleteTrigger(String id) { List<Trigger> triggers = this.triggersForId(id); synchronized (this._triggers) { this._triggers.removeAll(triggers); } return triggers.size() > 0; } public ArrayList<Bundle> allTriggersBundles(Context context) { ArrayList<Bundle> triggers = new ArrayList<>(); synchronized (this._triggers) { for (Trigger trigger : this._triggers) { triggers.add(trigger.bundle(context)); } } return triggers; } public void fireMissedTriggers(final Context context, long now) { final LongSparseArray<String> fireDates = new LongSparseArray<>(); for (Trigger trigger : this._triggers) { if (trigger instanceof DateTrigger) { DateTrigger dateTrig = (DateTrigger) trigger; if (dateTrig.missedFire(context, now)) { long lastFired = dateTrig.lastMissedFireTime(context); fireDates.put(lastFired, dateTrig.identifier()); } } } final TriggerManager me = this; Runnable r = new Runnable() { @Override public void run() { for (int i = 0; i < fireDates.size(); i++) { long time = fireDates.keyAt(i); for (Trigger t : me.triggersForId(fireDates.get(time))) { t.execute(context, true); // Wait 5 seconds for this trigger to complete so things // run in some semblance of "order" if each script runs // in 5 sec. or less... try { Thread.sleep(5000); } catch (InterruptedException e) { LogManager.getInstance(context).logException(e); } } } } }; Thread t = new Thread(r); t.start(); } @SuppressLint("Wakelock") public List<Long> upcomingFireTimes(Context context) { ArrayList<Long> upcoming = new ArrayList<>(); WakeLock wakeLock = WakeLockManager.getInstance(context).requestWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "trigger_wakelock"); long now = System.currentTimeMillis(); synchronized (this._triggers) { for (Trigger trigger : this._triggers) { if (trigger instanceof DateTrigger) { DateTrigger dateTrigger = (DateTrigger) trigger; upcoming.addAll(dateTrigger.fireTimes(context, now, now + (60 * 60 * 1000))); } } } Collections.sort(upcoming); WakeLockManager.getInstance(context).releaseWakeLock(wakeLock); return upcoming; } }